#include "cse452.h"
#include "MyBrush.h"
#include "BrushInterface.h"
#include <cmath>
#include <iostream>
#include <limits>
#include <algorithm>

void MyBrush::changedBrush() {
    // this is called anytime the brush type or brush radius changes
    // it should recompute the brush mask appropriately
    const int radius = brushUI->getRadius();

	if(MyBrush::mask != 0)
		deleteMask();
	MyBrush::maskSize = radius+radius+1;
	switch(brushUI->getBrushType()){
		case BRUSH_CONSTANT:
			maskConstant(radius);
			break;
		case BRUSH_LINEAR:
			maskLinear(radius);
			break;
		case BRUSH_QUADRATIC:
			maskQuadratic(radius);
			break;
		case BRUSH_GAUSSIAN:
			maskGaussian(radius);
			break;
		case BRUSH_NEGCONSTANT:
			maskConstant(radius);
			maskNegative(radius);
			break;
		case BRUSH_NEGLINEAR:
			maskLinear(radius);
			maskNegative(radius);
			break;
		case BRUSH_NEGQUADRATIC:
			maskQuadratic(radius);
			maskNegative(radius);
			break;
		case BRUSH_NEGGAUSSIAN:
			maskGaussian(radius);
			maskNegative(radius);
			break;
	}
}

void MyBrush::makeMask(){
	MyBrush::mask = new float*[MyBrush::maskSize];
	for(int i=0; i<MyBrush::maskSize; i++)
		MyBrush::mask[i] = new float[MyBrush::maskSize];
}

void MyBrush::deleteMask(){
	for(int i=0; i < MyBrush::maskSize; i++)
		delete MyBrush::mask[i];
	delete MyBrush::mask;
}

void MyBrush::maskConstant(const int radius){
	MyBrush::makeMask();
	int x,y;
	for(int i=0; i<MyBrush::maskSize; i++){
		y = i-radius;
		for(int j=0; j<MyBrush::maskSize; j++){
			x = j-radius;
			MyBrush::mask[i][j] = static_cast<float>((x*x+y*y < radius*radius) ? 1 : 0);
		}
	}
}
void MyBrush::maskLinear(const int radius){
	MyBrush::makeMask();
	int x,y;
	float val;
	for(int i=0; i<MyBrush::maskSize; i++){
		y = i-radius;
		for(int j=0; j<MyBrush::maskSize; j++){
			x = j-radius;
			val = (radius - sqrt(static_cast<float>(x*x+y*y)))/ radius;
			MyBrush::mask[i][j] = (val > 0) ? val : 0;
		}
	}
}
void MyBrush::maskQuadratic(const int radius){
	MyBrush::makeMask();
	float tmp, val;
	int x,y;
	for(int i=0; i<MyBrush::maskSize; i++){
		y = i-radius;
		for(int j=0; j<MyBrush::maskSize; j++){
			x = j-radius;
			tmp = (sqrt(static_cast<float>(x*x+y*y)))/ radius;
			val = 1 - tmp * tmp;
			MyBrush::mask[i][j] = (val > 0) ? val : 0;
		}
	}
}
void MyBrush::maskGaussian(const int radius){
	MyBrush::makeMask();
	int x,y;
	float val;
	for(int i=0; i<MyBrush::maskSize; i++){
		y = i-radius;
		for(int j=0; j<MyBrush::maskSize; j++){
			x = j-radius;
			val = 1 - exp(-((radius - sqrt(static_cast<float>(x*x+y*y)))/ radius));
			MyBrush::mask[i][j] = (val > 0) ? val : 0;
		}
	}
}

void MyBrush::maskNegative(const int radius){
	for(int i=0; i<MyBrush::maskSize; i++){
		for(int j=0; j<MyBrush::maskSize; j++){
			MyBrush::mask[i][j] = -MyBrush::mask[i][j];
		}
	}
}

void MyBrush::drawBrush( ) {
    // apply the current brush mask to image location (x,y)
    // the mouse location is in mouseDrag

    const int radius = brushUI->getRadius();
    const float pixelFlow = brushUI->getFlow();
    const Color colBrush = brushUI->getColor();

	int size = radius*2+1;
	int xmin = (mouseDrag.getX() - radius > 0) ? mouseDrag.getX() - radius : 0;
	int xmax = (mouseDrag.getX() + radius < MyBrush::screenWidth) ? mouseDrag.getX() + radius : MyBrush::screenWidth;
	int ymin = (mouseDrag.getY() - radius > 0) ? mouseDrag.getY() - radius : 0;
	int ymax = (mouseDrag.getY() + radius < MyBrush::screenHeight) ? mouseDrag.getY() + radius : MyBrush::screenHeight;

	int brushX;
	int brushY;
	for(int j=ymin; j<ymax; j++){
		brushY = j - mouseDrag.getY() + radius;
		for(int i=xmin; i<xmax; i++){
			brushX = i - mouseDrag.getX() + radius;
			putPixel(i, j, 
				MyBrush::mask[brushX][brushY] * pixelFlow * colBrush + (1 - MyBrush::mask[brushX][brushY] * pixelFlow) * getPixel(i,j));
		}
	}	
}

void MyBrush::drawLine( ) {
    // draw a thick line from mouseDown to mouseDrag
    // the width of the line is given by the current brush radius
    const int radius = brushUI->getRadius();
    const Color colBrush = brushUI->getColor();
}

//Assumes p1 always has a smaller x coordinate than p2
void drawSingleLine(ScreenPoint p1, ScreenPoint p2){
	
}

void MyBrush::drawCircle() {
    // draw a thick circle at mouseDown with radius r
    // the width of the circle is given by the current brush radius
	
}


void MyBrush::drawPolygon() {
	// draw a polygon with numVertices whos coordinates are stored in the
    // polygon array: {x0, y0, x1, y1, ...., xn-1, yn-1}

	const float pixelFlow = brushUI->getFlow();
	const Color colBrush = brushUI->getColor();


	//build all connects
	std::vector<Connect> lines;
	for(std::vector<ScreenPoint>::iterator it = polygon.begin(); it != polygon.end()-1; ++it){
		lines.push_back(Connect(*it, *(it + 1)));
	}
	lines.push_back(Connect(polygon.back(), polygon.front()));
	//sort connects
	std::sort(lines.begin(), lines.end());
	int minY = lines.begin()->p1.getY();
	int maxY = lines.back().p2.getY();
	std::vector<Connect> active;

	std::vector<Connect>::iterator nextLine = lines.begin();
	int lineCount = lines.size();
	int* scanPoints = static_cast<int*>(malloc(lineCount * sizeof(int)));
	//scan all connects drawing
	for(int scanline = minY; scanline < maxY; scanline++){
		//add new connectors to active
		while(nextLine->p1.getY() == scanline){
			active.push_back(*nextLine);
			nextLine++;
		}
		//iterate through active
		//remove old connectors
		int scanPointsCount = 0;
		for(std::vector<Connect>::iterator it = active.begin(); it != active.end(); it++){
			if(it->p2.getY() == scanline){
				active.erase(it);
				it--;
			}
			else{
				scanPoints[scanPointsCount] = static_cast<int>(floor(it->p1.getX() + it->getInvertSlope() * (scanline -it->p1.getY())));
				scanPointsCount++; 
			}
		}
		//find points to activate or deactivate on
		std::sort(scanPoints, scanPoints + scanPointsCount);
		//draw
		bool on = false;
		if(scanPointsCount > 0){
			int i = scanPoints[0];
			int nextPoint = 0;
			for(; i < screenWidth && i <= scanPoints[scanPointsCount-1]; i++){
				while(i == scanPoints[nextPoint]){
					on = !on;
					nextPoint++;
				}
				if(on == true){
					putPixel(i, scanline, pixelFlow * colBrush + (1 - pixelFlow) * getPixel(i,scanline));
				}
			}
		}
	}
	free(scanPoints);
	polygon.clear();
}

void MyBrush::filterRegion( ) {
    // apply the filter indicated by filterType to the square
    // defined by the two corner points mouseDown and mouseDrag
    // these corners are not guarenteed to be in any order
    // The filter width is given by the brush radius
}
